﻿using Ids4.Api.Application.Authrization;
using Ids4.Api.Application.Middleware;
using Ids4.Api.CustomerValidator;
using Ids4.Api.Data.Entities;
using Ids4.Api.Data.Mysql;
using Ids4.Api.Data.SqlServer;
using Ids4.Api.Models.Consts;
using Ids4.Api.Services.Impl;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.Filters;
using System.IdentityModel.Tokens.Jwt;
using System.Reflection;
using System.Security.Claims;
using System.Text;

namespace Ids4.Api.Application.Extensions
{
    /// <summary>
    /// SevicesExtension
    /// </summary>
    public static class SevicesExtension
    {
        private static IServiceCollection _services;

        private static void CheckSameSite(HttpContext httpContext, CookieOptions options)
        {
            if (options.SameSite == SameSiteMode.None)
            {
                var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
                if (DisallowsSameSiteNone(userAgent))
                {
                    // For .NET Core < 3.1 set SameSite = (SameSiteMode)(-1)
                    options.SameSite = SameSiteMode.Unspecified;
                }
            }
        }

        private static bool DisallowsSameSiteNone(string userAgent)
        {
            // Cover all iOS based browsers here. This includes:
            // - Safari on iOS 12 for iPhone, iPod Touch, iPad
            // - WkWebview on iOS 12 for iPhone, iPod Touch, iPad
            // - Chrome on iOS 12 for iPhone, iPod Touch, iPad
            // All of which are broken by SameSite=None, because they use the iOS networking stack
            if (userAgent.Contains("CPU iPhone OS 12") || userAgent.Contains("iPad; CPU OS 12"))
            {
                return true;
            }

            // Cover Mac OS X based browsers that use the Mac OS networking stack. This includes:
            // - Safari on Mac OS X.
            // This does not include:
            // - Chrome on Mac OS X
            // Because they do not use the Mac OS networking stack.
            if (userAgent.Contains("Macintosh; Intel Mac OS X 10_14") &&
                userAgent.Contains("Version/") && userAgent.Contains("Safari"))
            {
                return true;
            }

            // Cover Chrome 50-69, because some versions are broken by SameSite=None, 
            // and none in this range require it.
            // Note: this covers some pre-Chromium Edge versions, 
            // but pre-Chromium Edge does not require SameSite=None.
            if (userAgent.Contains("Chrome"))
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// AddConfig
        /// </summary>
        /// <param name="services"></param>
        /// <param name="configurationManager"></param>
        /// <returns></returns>
        public static IServiceCollection AddConfig(this IServiceCollection services, ConfigurationManager configurationManager)
        {
            services
                  .AddCommonSetup(configurationManager)
                  .AddSameSiteCookiePolicySetup()
                  .AddCorsSetup()
                  .AddSwaggerSetup(configurationManager)
                  .AddIdentityServerConfigureSetup(configurationManager)
                  .AddIdentityServerAuthenticationSetup();

            _services = services;

            return services;
        }

        /// <summary>
        /// AddCommonSetup
        /// </summary>
        /// <param name="services"></param>
        /// <param name="configurationManager"></param>
        /// <returns></returns>
        public static IServiceCollection AddCommonSetup(this IServiceCollection services, ConfigurationManager configurationManager)
        {
            services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
            services.AddControllersWithViews()
                 .AddRazorRuntimeCompilation()
                 .AddJsonOptions(options =>
                 {
                     options.JsonSerializerOptions.Converters.Add(new DateTimeConverter());

                 });
            //Url路径小写
            services.AddRouting(options => options.LowercaseUrls = true);
            services.Configure<OptionsSetting>(configurationManager);
            services.AddSingleton(new AppSettings(configurationManager));

            var Config = services.BuildServiceProvider().GetService<IOptions<OptionsSetting>>().Value;
            if (Config.DataBase.UseMinProfiler)
            {
                services.AddMiniProfiler(opt =>
                {
                    opt.RouteBasePath = "/profiler";
                }).AddEntityFramework();
            }
            return services;
        }

        /// <summary>
        /// AddSameSiteCookiePolicySetup
        /// </summary>
        /// <param name="services"></param>
        /// <returns></returns>
        public static IServiceCollection AddSameSiteCookiePolicySetup(this IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
                options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
                options.OnAppendCookie = cookieContext =>
                    CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
                options.OnDeleteCookie = cookieContext =>
                    CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
            });

            return services;
        }

        /// <summary>
        /// AddCorsSetup
        /// </summary>
        /// <param name="services"></param>
        /// <returns></returns>
        public static IServiceCollection AddCorsSetup(this IServiceCollection services)
        {
            services.AddCors(c =>
            {
                c.AddPolicy("Policy", policy =>
                {
                    policy.SetIsOriginAllowed((host) => true)
                        .AllowAnyMethod()
                        .AllowAnyHeader()
                        .AllowCredentials();
                });
            });
            return services;
        }

        /// <summary>
        /// AddSwaggerSetup
        /// </summary>
        /// <param name="services"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static IServiceCollection AddSwaggerSetup(this IServiceCollection services, ConfigurationManager configurationManager)
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }

            var identityserver4Config = services.BuildServiceProvider().GetService<IOptions<OptionsSetting>>().Value;
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo
                {
                    Title = "用户认证授权中心",
                    Version = "V1",
                    Description = "用户认证授权中心"
                });
                c.OperationFilter<AddResponseHeadersFilter>();
                c.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
                c.OperationFilter<SecurityRequirementsOperationFilter>();

                //// swagger ClientCredentials 客户端模式
                //c.AddSecurityDefinition("ClientCredentials", new OpenApiSecurityScheme
                //{
                //    Type = SecuritySchemeType.OAuth2,
                //    Flows = new OpenApiOAuthFlows
                //    {
                //        ClientCredentials = new OpenApiOAuthFlow
                //        {
                //            AuthorizationUrl = new Uri($"{identityserver4Config.IdentityServer4.AuthUrls}/connect/authorize"),
                //            TokenUrl = new Uri($"{identityserver4Config.IdentityServer4.AuthUrls}/connect/token"),
                //            Scopes = new Dictionary<string, string> {
                //                {
                //                    "IdentityServer.API","IdentityServer4 API "
                //                }
                //            }
                //        }
                //    }
                //});

                // swagger  Password 用户密码模式
                c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
                {
                    Type = SecuritySchemeType.OAuth2,
                    Flows = new OpenApiOAuthFlows
                    {
                        Password = new OpenApiOAuthFlow
                        {
                            AuthorizationUrl = new Uri($"{identityserver4Config.IdentityServer4.AuthUrls}/connect/authorize"),
                            TokenUrl = new Uri($"{identityserver4Config.IdentityServer4.AuthUrls}/connect/token"),
                            Scopes = new Dictionary<string, string> {
                                {
                                    "IdentityServer.API","IdentityServer4 API "
                                }
                            }
                        }
                    }
                });

                //// swagger Implicit 隐式模式
                //c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
                //{
                //    Type = SecuritySchemeType.OAuth2,
                //    Flows = new OpenApiOAuthFlows
                //    {
                //        Implicit = new OpenApiOAuthFlow
                //        {
                //            AuthorizationUrl = new Uri($"{identityserver4Config.IdentityServer4.AuthUrls}/connect/authorize"),
                //            Scopes = new Dictionary<string, string> {
                //                {
                //                    "IdentityServer.API","IdentityServer4 API "
                //                }
                //            }
                //        }
                //    }
                //});

                ////swagger AuthorizationCode 授权码模式
                //c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
                //{
                //    Type = SecuritySchemeType.OAuth2,
                //    Flows = new OpenApiOAuthFlows
                //    {
                //        AuthorizationCode = new OpenApiOAuthFlow
                //        {
                //            AuthorizationUrl = new Uri($"{identityserver4Config.IdentityServer4.AuthUrls}/connect/authorize"),
                //            TokenUrl = new Uri($"{identityserver4Config.IdentityServer4.AuthUrls}/connect/token"),
                //            Scopes = new Dictionary<string, string> {
                //                {
                //                    "IdentityServer.API","IdentityServer4 API "
                //                }
                //            }
                //        }
                //    }
                //});

                string XmlAPIFilePath = Path.Combine(System.AppContext.BaseDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.xml");
                c.IncludeXmlComments(XmlAPIFilePath, true);
            });

            return services;
        }

        /// <summary>
        /// AddIdentityServerConfigureSetup
        /// </summary>
        /// <param name="services"></param>
        /// <param name="configurationManager"></param>
        /// <returns></returns>
        public static IServiceCollection AddIdentityServerConfigureSetup(this IServiceCollection services, ConfigurationManager configurationManager)
        {
            var conf = services.BuildServiceProvider().GetService<IOptions<OptionsSetting>>().Value;

            if (conf.DataBase.Type.ToLower() == DatabaseConst.Mysql)
            {
                //注册asp.net Identity
                services.ConfigureIdentityByMysql(conf.DataBase.ConnectStr);
                //注册Identityserver4认证服务
                services.ConfigureIdentityServerByMysql(conf.DataBase.ConnectStr);
            }
            else
            {
                //注册asp.net Identity
                services.ConfigureIdentityBySqlServer(conf.DataBase.ConnectStr);
                //注册Identityserver4认证服务
                services.ConfigureIdentityServerBySqlServer(conf.DataBase.ConnectStr);
            }

            services.ConfigureApplicationCookie(options =>
            {
                options.AccessDeniedPath = new PathString("/oauth2/accessdenied");
                options.Cookie.Name = "Cookie";
                options.Cookie.HttpOnly = true;
                options.ExpireTimeSpan = TimeSpan.FromMinutes(720);
                options.LoginPath = new PathString("/oauth2/authorize");
                options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
                options.SlidingExpiration = true;
            });

            return services;
        }
        public static void ConfigureIdentityBySqlServer(this IServiceCollection services, string connectionString)
        {
            services.AddDbContext<SqlServerApplicationDbContext>(options =>
            {
                options.UseSqlServer(connectionString);
            });

            services.AddIdentity<ApplicationUser, ApplicationRole>(opt =>
            {
                opt.Password.RequireLowercase = false;
                opt.Password.RequireNonAlphanumeric = false;
                opt.Password.RequireUppercase = false;
                opt.Password.RequiredLength = 6;
            })
            .AddEntityFrameworkStores<SqlServerApplicationDbContext>()
            .AddDefaultTokenProviders();
        }
        public static void ConfigureIdentityByMysql(this IServiceCollection services, string connectionString)
        {
            services.AddDbContext<MysqlApplicationDbContext>(options =>
            {
                options.UseMySql(connectionString, MySqlServerVersion.LatestSupportedServerVersion);
            });

            services.AddIdentity<ApplicationUser, ApplicationRole>(opt =>
            {
                opt.Password.RequireLowercase = false;
                opt.Password.RequireNonAlphanumeric = false;
                opt.Password.RequireUppercase = false;
                opt.Password.RequiredLength = 6;
            })
            .AddEntityFrameworkStores<MysqlApplicationDbContext>()
            .AddDefaultTokenProviders();
        }
        public static void ConfigureIdentityServerBySqlServer(this IServiceCollection services, string connectionString)
        {
            var migrationAssembly = typeof(ApplicationUser).GetTypeInfo().Assembly.GetName().Name;

            services.AddIdentityServer(options =>
            {
                options.Events.RaiseErrorEvents = true;
                options.Events.RaiseInformationEvents = true;
                options.Events.RaiseFailureEvents = true;
                options.Events.RaiseSuccessEvents = true;
            }).AddDeveloperSigningCredential()
              .AddConfigurationStore<SqlServerConfigurationDbContext>(options =>
              {
                  options.ConfigureDbContext = builder =>
                  {
                      builder.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationAssembly));
                  };
              })
                .AddOperationalStore<SqlServerPersistedGrantDbContext>(options =>
                {
                    options.ConfigureDbContext = builder =>
                    {
                        builder.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationAssembly));
                    };
                    options.EnableTokenCleanup = true;
                })
                .AddAspNetIdentity<ApplicationUser>()
                .AddExtensionGrantValidator<SmsAuthCodeValidator>()//短信扩展验证
                .AddProfileService<CustomProfileService<ApplicationUser>>();
            //.AddResourceOwnerValidator<CustomResourceOwnerPasswordValidtor<ApplicationUser, ApplicationRole>>()

        }
        public static void ConfigureIdentityServerByMysql(this IServiceCollection services, string connectionString)
        {
            var migrationAssembly = typeof(MysqlConfigurationDbContext).GetTypeInfo().Assembly.GetName().Name;
            services.AddIdentityServer(options =>
            {
                options.Events.RaiseErrorEvents = true;
                options.Events.RaiseInformationEvents = true;
                options.Events.RaiseFailureEvents = true;
                options.Events.RaiseSuccessEvents = true;
            }).AddDeveloperSigningCredential()
              .AddConfigurationStore<MysqlConfigurationDbContext>(options =>
              {
                  options.ConfigureDbContext = builder =>
                  {
                      builder.UseMySql(connectionString, MySqlServerVersion.LatestSupportedServerVersion,
                          sql =>
                          {
                              sql.MigrationsAssembly(migrationAssembly);
                          });
                  };
              }).AddOperationalStore<MysqlPersistedGrantDbContext>(options =>
              {
                  options.ConfigureDbContext = builder =>
                  {
                      builder.UseMySql(connectionString, MySqlServerVersion.LatestSupportedServerVersion,
                          sql =>
                          {
                              sql.MigrationsAssembly(migrationAssembly);
                          });
                      options.EnableTokenCleanup = true;
                  };

              })
              .AddAspNetIdentity<ApplicationUser>()
              .AddExtensionGrantValidator<SmsAuthCodeValidator>()//短信扩展验证
              .AddProfileService<CustomProfileService<ApplicationUser>>();
            //.AddResourceOwnerValidator<CustomResourceOwnerPasswordValidtor<ApplicationUser, ApplicationRole>>()
        }

        public static IServiceCollection AddIdentityServerAuthenticationSetup(this IServiceCollection services)
        {
            var identityserver4Config = services.BuildServiceProvider().GetService<IOptions<OptionsSetting>>().Value;
            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

            var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(identityserver4Config.IdentityServer4.SigningKey));
            var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);

            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
             .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
             {
                 options.Authority = identityserver4Config.IdentityServer4.AuthUrls;
                 options.RequireHttpsMetadata = false;
                 options.Audience = identityserver4Config.IdentityServer4.Audience;
                 options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                 {
                     RoleClaimType = "role",
                     SaveSigninToken = true,
                     ValidateIssuerSigningKey = true,   //是否验证SecurityKey
                     IssuerSigningKey = signingKey,  //拿到SecurityKey
                     ValidateIssuer = false, //是否验证Issuer
                     ValidIssuer = identityserver4Config.IdentityServer4.Issuer,//发行人 //Issuer，这两项和前面签发jwt的设置一致
                     ValidateAudience = false, //是否验证Audience
                     ValidAudience = identityserver4Config.IdentityServer4.Audience,//订阅人
                     ValidateLifetime = true,//是否验证失效时间
                     ClockSkew = TimeSpan.FromSeconds(60),
                     RequireExpirationTime = true,
                 };
                 options.Events = new JwtBearerEvents
                 {
                     //接受到消息时调用
                     OnMessageReceived = context => Task.CompletedTask,
                     //在Token验证通过后调用
                     OnTokenValidated = context =>
                     {
                         //var AuthenticationInfo = context.HttpContext.RequestServices.GetService<AuthenticationInfo>();
                         var claims = context.Principal.Claims;
                         //AuthenticationInfo.Id = long.Parse(claims.First(x => x.Type == JwtRegisteredClaimNames.NameId).Value);
                         //AuthenticationInfo.Account = claims.First(x => x.Type == JwtRegisteredClaimNames.UniqueName).Value;
                         //AuthenticationInfo.Name = claims.First(x => x.Type == JwtRegisteredClaimNames.Name).Value;
                         //AuthenticationInfo.RemoteIpAddress = context.HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString();
                         return Task.CompletedTask;
                     },
                     //认证失败时调用
                     OnAuthenticationFailed = context =>
                     {
                         var token = context.Request.Headers["Authorization"].ObjectToString().Replace("Bearer ", "");
                         var jwtToken = (new JwtSecurityTokenHandler()).ReadJwtToken(token);

                         if (jwtToken.Issuer != identityserver4Config.IdentityServer4.Issuer)
                         {
                             context.Response.Headers.Add("Token-Error-Iss", "issuer is wrong!");
                         }

                         if (jwtToken.Audiences.FirstOrDefault() != identityserver4Config.IdentityServer4.Audience)
                         {
                             context.Response.Headers.Add("Token-Error-Aud", "Audience is wrong!");
                         }

                         if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                         {
                             context.Response.Headers.Add("Token-Expired", "true");
                         }

                         return Task.CompletedTask;
                     },
                     //未授权时调用
                     OnChallenge = context =>
                     {
                         context.Response.Headers.Add("Token-Error", context.ErrorDescription);
                         return Task.CompletedTask;
                     }
                 };
                 IdentityModelEventSource.ShowPII = true;
             });

            // 数据库动态绑定，处理器里动态赋值
            var permission = new List<PermissionItem>();
            // 角色与接口的权限要求参数
            var permissionRequirement = new PermissionRequirement(
                "/api/denied",// 拒绝授权的跳转地址（目前无用）
                permission,
                ClaimTypes.Role,//基于角色的授权
                identityserver4Config.IdentityServer4.Issuer,//发行人
                identityserver4Config.IdentityServer4.Audience,//听众
                signingCredentials,//签名凭据
                expiration: TimeSpan.FromSeconds(60 * 60 * 24)//接口的过期时间
                );

            // 复杂的策略授权
            services.AddAuthorization(options =>
            {
                options.AddPolicy(Permissions.Name,
                         policy => policy.Requirements.Add(permissionRequirement));
            });

            // 注入权限处理器
            services.AddScoped<IAuthorizationHandler, PermissionHandler>();

            #region MyRegion
            //// API授权
            //services.AddAuthorization(options =>
            //{
            //    options.AddPolicy("IdentityServer", policy =>
            //    {
            //        policy.RequireAuthenticatedUser();
            //        policy.RequireClaim("scope", "IdentityServer");
            //    });
            //});
            #endregion

            return services;
        }

        /// <summary>
        /// UseConfig
        /// </summary>
        /// <param name="app"></param>
        /// <returns></returns>
        public static WebApplication UseConfig(this WebApplication app)
        {
            app.UseMiniProfiler();
            app.UseCors("Policy");
            app.UseCookiePolicy();
            app.UseMiddleware<GlobalExceptionMildd>();
            app.UseAllServicesMildd(_services);
            app.UseStaticFiles();
            app.UseRouting();
            app.UseIdentityServer();
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseSwagger(c =>
            {
                c.PreSerializeFilters.Add((swagger, httpReq) =>
                {
                    swagger.Servers = new List<OpenApiServer> {
                        new OpenApiServer
                        {
                            Url = $"{httpReq.Scheme}://{httpReq.Host.Value}"
                        }
                    };
                });
            });
            app.UseSwaggerUI(c =>
            {
                var assembly = Assembly.GetExecutingAssembly();
                c.SwaggerEndpoint("/swagger/v1/swagger.json", $"{assembly.GetName().Name}");
                c.IndexStream = () => assembly.GetManifestResourceStream($"{assembly.GetName().Name}.swaggerIndex.html");
            });
            app.UseReDoc();
            app.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
            return app;
        }
    }
}
